/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package sistema;

import beans.Usuario;
import exceptions.AtributoNaoPreenchidoException;
import exceptions.LoginInvalidoException;
import exceptions.SenhaInvalidaException;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import persistencia.Persistencia;

/**
 *
 * @author TalesHugo
 */
public class Sistema {

    // Estrutura de dados MAP contendo como chave uma String e como valor um Objeto do tipo Usuário
    private Map<String,Usuario> users;
    //Implementando log4j
    private Logger logger = Logger.getLogger(Sistema.class);
    //Persistência
    private Persistencia persistencia = new Persistencia();

    /**
     * Construtor da classe Sistema.
     */
    public Sistema(){
        users = new HashMap<String, Usuario>();
        PropertyConfigurator.configure("log4j/log4j.properties");
    }

    /**
     * Método que adiciona um usuário a estrutura de dados Map users.
     * @param login Login do usuário a ser adicionado.
     * @param senha Senha do usuário a ser adicionado.
     * @param nome Nome do usuário a ser adicionado.
     * @throws SenhaInvalidaException Exceção lançada caso a senha seja nula ou não possua caracteres ou tenha menos de 6 caracteres.
     * @throws LoginInvalidoException Exceção lançada caso o login já pertencer a algum usuário dentro do sistema ou caso o login seja nulo ou não possua caracteres.
     */
    public void criarUsuario(String login, String senha, String nome) throws SenhaInvalidaException, LoginInvalidoException {
        if (login == null || login.equals("")) {
            logger.info("Método criaUsuario - Usuário com login:" + login + " || senha:" + senha + " || nome:" + nome + " - Exception Login Inválido.");
            throw new LoginInvalidoException("Login inválido.");
        }
        if (senha == null || senha.length() <= 5 || senha.equals("")) {
            logger.info("Método criaUsuario - Usuário com login:" + login + " || senha:" + senha + " || nome:" + nome + " - Exception Senha deve conter mais do que 5 caracteres.");
            throw new SenhaInvalidaException("Senha deve conter mais do que 5 caracteres.");
        }
        if (contemUsuario(login)) {
            logger.info("Método criaUsuario - Usuário com login:" + login + " || senha:" + senha + " || nome:" + nome + " - Exception Conta com esse nome já existe.");
            throw new LoginInvalidoException("Conta com esse nome já existe.");
        }
        logger.info("Método criaUsuario - Usuário com login:" + login + " || senha:" + senha + " || nome:" + nome + " - Criação efetuada com sucesso.");
        Usuario user = new Usuario(login, senha, nome);
        this.users.put(login, user);
    }

    /**
     * Método que checa se um usuário está dentro da estrutura de dados Map users.
     * @param login Login do usuário a ser checado.
     * @return TRUE se ele está contido no Map users e FALSE caso contrário.
     */
    public boolean contemUsuario(String login){
        return this.users.containsKey(login);
    }

    /**
     * Método que verifica se o usuário está contido dentro da estrutura Map como também se o valor da senha passada como argumento é realmente a senha deste usuário.
     * @param login Login do usuário a ser verificado.
     * @param senha Senha do usuário a ser verificado.
     * @return Usuário que realmente está dentro da estrutura Map e teve sua senha confirmada.
     * @throws SenhaInvalidaException Exceção lançada caso a senha não pertença a nenhum usuário dentro do sistema.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
     */
    private Usuario validaSenhaUser(String login, String senha) throws SenhaInvalidaException, LoginInvalidoException{
        if (contemUsuario(login)){
            Usuario user = this.users.get(login);
            if (user.getSenha().equals(senha)){
                return user;
            } throw new SenhaInvalidaException();
        }
        throw new LoginInvalidoException("Usuário não cadastrado.");
    }

    /**
     * Método que adiciona um valor a descrição de um usuário.
     * @param login Login do usuário a ser adicionado um valor a sua descrição.
     * @param senha Senha do usuário a ser adicionado um valor a sua descrição.
     * @param descricao Descrição do usuário a ser modificada.
     * @throws SenhaInvalidaException Exceção lançada caso a senha não pertença a nenhum usuário dentro do sistema.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
     */
    public void editarDescricao(String login, String senha, String descricao) throws SenhaInvalidaException, LoginInvalidoException {
        logger.info("Método editarDescricao - Usuário de login:" + login + " || senha:" + senha + " || nova descrição:" + descricao);
        Usuario user = validaSenhaUser(login, senha);
        user.setDescricao(descricao);
    }

    /**
     * Método que retorna uma String contendo a descrição de um usuário.
     * @param login Login do usuário que terá sua descrição retornada.
     * @param senha Senha do usuário que terá sua descrição retornada.
     * @return String contendo a descrição do usuário.
     * @throws AtributoNaoPreenchidoException Exceção lançada caso a descrição do usuário for nula ou não possua caracteres.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
     * @throws SenhaInvalidaException Exceção lançada caso a senha não pertença a nenhum usuário dentro do sistema.
     */
    public String getDescricao(String login, String senha) throws AtributoNaoPreenchidoException, LoginInvalidoException, SenhaInvalidaException{
        Usuario user = validaSenhaUser(login, senha);
        String descricao = user.getDescricao();
        if (descricao == null || descricao.equals("")) {
            logger.info("Método getDescricao - Usuário de login:" + login + " || senha:" + senha + " || descrição retornada:" + descricao + " - Exception Atributo não preenchido.");
            throw new AtributoNaoPreenchidoException();
        }
        logger.info("Método getDescricao - Usuário de login:" + login + " || senha:" + senha + " || descrição retornada:" + descricao + " - Efetuado com sucesso.");
        return descricao;
    }

    /**
     * Método que adiciona um valor ao estado civil de um usuário.
     * @param login Login do usuário a ser adicionado um valor a seu estado civil.
     * @param senha Senha do usuário a ser adicionado um valor a seu estado civil.
     * @param estado Estado Civil do usuário a ser modificado.
     * @throws SenhaInvalidaException Exceção lançada caso a senha não pertença a nenhum usuário dentro do sistema.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
     */
    public void editarEstadoCivil(String login, String senha, String estado) throws SenhaInvalidaException, LoginInvalidoException{
        logger.info("Método editarEstadoCivil - Usuário de login:" + login + " || senha:" + senha + " || nova descrição:" + estado);
        Usuario user = validaSenhaUser(login, senha);
        user.setEstadoCivil(estado);
    }

    /**
     * Método que retorna uma String contendo o estado civil de um usuário.
     * @param login Login do usuário que terá seu estado civil retornado.
     * @param senha Senha do usuário que terá seu estado civil retornado.
     * @return String contendo o estado civil do usuário.
     * @throws AtributoNaoPreenchidoException Exceção lançada caso o estado civil do usuário for nulo ou não possua caracteres.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
     * @throws SenhaInvalidaException Exceção lançada caso a senha não pertença a nenhum usuário dentro do sistema.
     */
    public String getEstadoCivil(String login, String senha) throws AtributoNaoPreenchidoException, LoginInvalidoException, SenhaInvalidaException {
        Usuario user = validaSenhaUser(login, senha);
        String estado = user.getEstadoCivil();
        if (estado == null || estado.equals("")) {
            logger.info("Método getEstadoCivil - Usuário de login:" + login + " || senha:" + senha + " || estado civil retornado:" + estado + " - Exception Atributo não preenchido.");
            throw new AtributoNaoPreenchidoException();
        }
        logger.info("Método getEstadoCivil - Usuário de login:" + login + " || senha:" + senha + " || estado civil retornado:" + estado + " - Efetuado com sucesso.");
        return estado;
    }

    /**
     * Método que adiciona um valor ao aniversário de um usuário.
     * @param login Login do usuário a ser adicionado um valor a seu aniversário.
     * @param senha Senha do usuário a ser adicionado um valor a seu aniversário.
     * @param data Aniversário do usuário a ser modificado.
     * @throws SenhaInvalidaException Exceção lançada caso a senha não pertença a nenhum usuário dentro do sistema.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
     */
    public void editarAniversario(String login, String senha, String data) throws SenhaInvalidaException, LoginInvalidoException {
        logger.info("Método editarAniversario - Usuário de login:" + login + " || senha:" + senha + " || nova descrição:" + data);
        Usuario user = validaSenhaUser(login, senha);
        user.setAniversario(data);
    }

    /**
     * Método que retorna uma String contendo o aniversário de um usuário.
     * @param login Login do usuário que terá seu aniversário retornado.
     * @param senha Senha do usuário que terá seu aniversário retornado.
     * @return String contendo o aniversário do usuário.
     * @throws AtributoNaoPreenchidoException Exceção lançada caso o aniversário do usuário for nulo ou não possua caracteres.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
     * @throws SenhaInvalidaException Exceção lançada caso a senha não pertença a nenhum usuário dentro do sistema.
     */
    public String getAniversario(String login, String senha) throws AtributoNaoPreenchidoException, LoginInvalidoException, SenhaInvalidaException {
        Usuario user = validaSenhaUser(login, senha);
        String aniversario = user.getAniversario();
        if (aniversario == null || aniversario.equals("")) {
            logger.info("Método getAniversario - Usuário de login:" + login + " || senha:" + senha + " || aniversário retornado:" + aniversario + " - Exception Atributo não preenchido.");
            throw new AtributoNaoPreenchidoException();
        }
        logger.info("Método getAniversario - Usuário de login:" + login + " || senha:" + senha + " || aniversário retornado:" + aniversario + " - Efetuado com sucesso.");
        return aniversario;
    }

    /**
     * Método que adiciona um valor a cidade natal de um usuário.
     * @param login Login do usuário a ser adicionado um valor a sua cidade natal.
     * @param senha Senha do usuário a ser adicionado um valor a sua cidade natal.
     * @param cidade Cidade Natal do usuário a ser modificada.
     * @throws SenhaInvalidaException Exceção lançada caso a senha não pertença a nenhum usuário dentro do sistema.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
     */
    public void editarCidadeNatal(String login, String senha, String cidade) throws SenhaInvalidaException, LoginInvalidoException {
        logger.info("Método editarCidadeNatal - Usuário de login:" + login + " || senha:" + senha + " || nova descrição:" + cidade);
        Usuario user = validaSenhaUser(login, senha);
        user.setCidadeNatal(cidade);
    }

    /**
     * Método que retorna uma String contendo a cidade natal de um usuário.
     * @param login Login do usuário que terá sua cidade natal retornada.
     * @param senha Senha do usuário que terá sua cidade natal retornada.
     * @return String contendo a cidade natal do usuário.
     * @throws AtributoNaoPreenchidoException Exceção lançada caso a a cidade natal do usuário for nula ou não possua caracteres.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
     * @throws SenhaInvalidaException Exceção lançada caso a senha não pertença a nenhum usuário dentro do sistema.
     */
    public String getCidadeNatal(String login, String senha) throws AtributoNaoPreenchidoException, LoginInvalidoException, SenhaInvalidaException {
        Usuario user = validaSenhaUser(login, senha);
        String cidadeNatal = user.getCidadeNatal();
        if (cidadeNatal == null || cidadeNatal.equals("")) {
            logger.info("Método getCidadeNatal - Usuário de login:" + login + " || senha:" + senha + " || cidade natal retornada:" + cidadeNatal + " - Exception Atributo não preenchido.");
            throw new AtributoNaoPreenchidoException();
        }
        logger.info("Método getCidadeNatal - Usuário de login:" + login + " || senha:" + senha + " || cidade natal retornada:" + cidadeNatal + " - Efetuado com sucesso.");
        return cidadeNatal;
    }

    /**
     * Método que retorna uma String contendo o nome de um usuário.
     * @param login Login do usuário a ser retornado o nome.
     * @return Nome do usuário.
     * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema ou caso o login seja nulo ou não possua caracteres.
     */
    public String getNomeUsuario(String login) throws LoginInvalidoException {
        if (login == null || login.equals("")) {
            logger.info("Método getNomeUsuario - Usuário com login:" + login + " - Exception Login Inválido.");
            throw new LoginInvalidoException();
        }
        if (!contemUsuario(login)) {
            logger.info("Método getNomeUsuario - Usuário com login:" + login + " - Exception Usuário não cadastrado.");
            throw new LoginInvalidoException("Usuário não cadastrado.");
        }
        logger.info("Método getNomeUsuario - Usuário com login:" + login + " - Retorno efetuado com sucesso.");
        return this.users.get(login).getNome();
    }

    /**
     * Método que verifica se um usuário realmente é amigo de outro usuário.
     * @param login Login do usuário a ser verificado.
     * @param amigo Login do usuário que deverá ser amigo do usuário a ser verificado.
     * @return TRUE se realmente o usuário a ser verificado contem dentro de sua lista de amigos o outro usuário, FALSE caso contrário.
     */
   public boolean ehAmigo(String login, String amigo) {
       Usuario user1 = this.users.get(login);
       Usuario user2 = this.users.get(amigo);
       if (user1.contemAmizade(user1, user2)){
           return true;
       }
       return false;
   }

   /**
    * Método que adiciona um usuário da lista de amigos de outro usuário.
    * @param id Login do usuário que adicionará dentro de sua lista de amigos outro usuário.
    * @param amigo Login do usuário a ser solicitado adição.
    * @throws LoginInvalidoException Exceção lançada caso o login(id) ou o login(amigo) não pertencerem a nenhum usuário dentro do sistema ou se id e amigo tiverem valores iguais ou se espera aceitação de convite ou se o usuário já for amigo do outro usuário.
    */
   public void adicionarAmigo(String id, String amigo) throws LoginInvalidoException {
       if (!contemUsuario(id) || !contemUsuario(amigo)) {
           logger.info("Método adicionarAmigo - Usuário com login:" + id + " || adiciona o amigo:" + amigo + " - Exception Usuário não cadastrado.");
           throw new LoginInvalidoException("Usuário não cadastrado.");
       }
       if (id.equals(amigo)) {
           logger.info("Método adicionarAmigo - Usuário com login:" + id + " || adiciona o amigo:" + amigo + " - Exception Usuário não pode adicionar a si mesmo como amigo.");
           throw new LoginInvalidoException("Usuário não pode adicionar a si mesmo como amigo.");
       }
       if (ehAmigo(id, amigo)) {
           logger.info("Método adicionarAmigo - Usuário com login:" + id + " || adiciona o amigo:" + amigo + " - Exception Usuário já está adicionado como amigo.");
           throw new LoginInvalidoException("Usuário já está adicionado como amigo.");
       }
       Usuario user1 = this.users.get(id);
       Usuario user2 = this.users.get(amigo);
       if (user1.contemPedido(user1, user2)) {
           logger.info("Método adicionarAmigo - Usuário com login:" + id + " || adiciona o amigo:" + amigo + " - Exception Usuário já está adicionado como amigo, esperando aceitação do convite.");
           throw new LoginInvalidoException("Usuário já está adicionado como amigo, esperando aceitação do convite.");
       }
       user1.solicitaAdicaoAmigo(user2);
       if (user2.aceitaPedido(user1, user2)){
           logger.info("Método adicionarAmigo - Usuário com login:" + id + " || adiciona o amigo:" + amigo + " - Adição efetuada com sucesso.");
           user1.adicionaAmigo(user2);
           user2.adicionaAmigo(user1);
       }
   }

   /**
    * Método que retorna uma String formatada de amigos contendo o login de todos os amigos do usuário.
    * @param login Login do usuário que terá a String formatada de amigos retornada.
    * @return String formatada de amigos contendo o login de todos os amigos do usuário.
    * @throws LoginInvalidoException Exceção lançada caso o login não pertença a nenhum usuário dentro do sistema.
    */
    public String getAmigos(String login) throws LoginInvalidoException {
        if (!contemUsuario(login)) {
            logger.info("Método getAmigos - Usuário com login:" + login + " - Exception Login inválido.");
            throw new LoginInvalidoException();
        }
        logger.info("Método getAmigos - Usuário com login:" + login + " - Retorno efetuado com sucesso.");
        Usuario user = this.users.get(login);
        return manipulaAmigosUser(user.getAmigos());
    }

    /**
     * Método que manipula a lista de amigos do usuário e retorna uma String contendo o login de todos os amigos do usuário de forma manipulada.
     * @param amigos Lista de amigos do usuário.
     * @return String contendo o login de todos os amigos do usuário de forma manipulada.
     */
    public String manipulaAmigosUser(List<Usuario> amigos){
        String resultado = "{";
        for (Usuario user : amigos){
            resultado += user.getLogin() + ",";
        }
        int tamanho = resultado.length();
        if (tamanho == 1){
            return resultado += "}";
        } else{
            resultado = resultado.substring(0, tamanho - 1);
            return resultado += "}";
        }
    }

    /**
    * Método que remove um usuário da lista de amigos de outro usuário.
    * @param id Login do usuário que removerá dentro de sua lista de amigos outro usuário.
    * @param amigo Login do usuário a ser removido.
    * @throws LoginInvalidoException Exceção lançada caso o login(id) ou o login(amigo) não pertencerem a nenhum usuário dentro do sistema ou se o login(id) e o login(amigo) não forem amigos de fato amigos.
    */
    public void removerAmizade(String id, String amigo) throws LoginInvalidoException {
        if (!contemUsuario(id) || !contemUsuario(amigo)) {
            logger.info("Método removerAmizade - Usuário com login:" + id + " || remove o amigo:" + amigo + " - Exception Usuário não cadastrado.");
            throw new LoginInvalidoException("Usuário não cadastrado.");
        }
        if (!ehAmigo(id, amigo)) {
            logger.info("Método removerAmizade - Usuário com login:" + id + " || remove o amigo:" + amigo + " - Exception Usuários não são amigos.");
            throw new LoginInvalidoException("Usuários não são amigos");
        }
        logger.info("Método adicionarAmigo - Usuário com login:" + id + " || adiciona o amigo:" + amigo + " - Remoção efetuada com sucesso.");
        Usuario user1 = this.users.get(id);
        Usuario user2 = this.users.get(amigo);
        user1.removeAmigo(user2);
        user2.removeAmigo(user1);
    }

    /**
     * Método responsável por limpar o map de usuários do sistema e também fazer a persistência dele no sistema.
     */
    public void encerrarSistema() throws IOException, ClassNotFoundException {
        this.persistencia.gravaObjetoArquivo(users);
        this.users.clear();
        users = this.persistencia.lerObjetoArquivo();
    }

    
}
